#include "sobel.h"

#include <stdlib.h>
#include <math.h>

/*
 * Transforms the rgb information of an image stored in buffer to it's gray
 * representation
 */
void rgbToGray(byte rgb[RGB_SIZE], byte gray[IMG_PIXELS])
{
    // Calculate the value for every pixel in gray
    for(int i = 0; i < IMG_PIXELS; i++) {
        byte r = rgb[3*i + 0];
        byte g = rgb[3*i + 1];
        byte b = rgb[3*i + 2];
        gray[i] = (byte)(0.30*r + 0.59*g + 0.11*b);
    }
}

/*
 * Make the operation memory for iterative convolution
 */
void makeOpMem(byte buffer[IMG_PIXELS], int cindex, byte op_mem[SOBEL_OP_SIZE]) {
    int bottom = cindex - IMG_WIDTH < 0;
    int top = cindex + IMG_WIDTH >= IMG_PIXELS;
    int left = cindex % IMG_WIDTH == 0;
    int right = (cindex+1) % IMG_WIDTH == 0;

    op_mem[0] = !bottom && !left  ? buffer[cindex-IMG_WIDTH-1] : 0;
    op_mem[1] = !bottom           ? buffer[cindex-IMG_WIDTH]   : 0;
    op_mem[2] = !bottom && !right ? buffer[cindex-IMG_WIDTH+1] : 0;

    op_mem[3] = !left             ? buffer[cindex-1]       : 0;
    op_mem[4] = buffer[cindex];
    op_mem[5] = !right            ? buffer[cindex+1]       : 0;

    op_mem[6] = !top && !left     ? buffer[cindex+IMG_WIDTH-1] : 0;
    op_mem[7] = !top              ? buffer[cindex+IMG_WIDTH]   : 0;
    op_mem[8] = !top && !right    ? buffer[cindex+IMG_WIDTH+1] : 0;
}

/*
 * Performs convolution between first two arguments
 */
int convolution(byte X[SOBEL_OP_SIZE], int Y[SOBEL_OP_SIZE])
{
    int sum = 0;

    for(int i=0; i<SOBEL_OP_SIZE; i++) {
        sum += X[i] * Y[SOBEL_OP_SIZE-i-1];
    }

    return sum;
}

/*
 * Iterate Convolution
 */
void itConv(byte buffer[IMG_PIXELS], int op[SOBEL_OP_SIZE], byte res[IMG_PIXELS]) {
    // Temporary memory for each pixel operation
    byte op_mem[SOBEL_OP_SIZE];

    // Make convolution for every pixel
    for(int i=0; i<IMG_PIXELS; i++) {
        // Make op_mem
        makeOpMem(buffer, i, op_mem);

        // Convolution
        int conv = convolution(op_mem, op);
        res[i] = (byte)(conv > 0 ? conv : -conv); // abs
        /*
         * The abs function is used in here to avoid storing negative numbers
         * in a byte data type array. It wouldn't make a different if the negative
         * value was to be stored because the next time it is used the value is
         * squared.
         */
    }
}

/*
 * Contour
 */
void contour(byte sobel_h[IMG_PIXELS], byte sobel_v[IMG_PIXELS], byte contour_img[IMG_PIXELS])
{
    // Iterate through every pixel to calculate the contour image
    for(int i=0; i<IMG_PIXELS; i++) {
        int gx = sobel_h[i];
        int gy = sobel_v[i];
        contour_img[i] = (byte)(sqrtf(gx*gx + gy*gy));
    }
}

void sobelFilter(
    byte rgb[RGB_SIZE],
    byte gray[IMG_PIXELS],
    byte sobel_h_res[IMG_PIXELS],
    byte sobel_v_res[IMG_PIXELS],
    byte contour_img[IMG_PIXELS])
{
    // sobel kernel
    int sobel_h[SOBEL_OP_SIZE] = {-1, 0, 1, -2, 0, 2, -1, 0, 1},
        sobel_v[SOBEL_OP_SIZE] = {1, 2, 1, 0, 0, 0, -1, -2, -1};

    // Get gray representation of the image
    rgbToGray(rgb, gray);

    // Make sobel operations
    itConv(gray, sobel_h, sobel_h_res);
    itConv(gray, sobel_v, sobel_v_res);

    // Calculate contour matrix
    contour(sobel_h_res, sobel_v_res, contour_img);
}
